require "spec"
require "../src/luce"
require "../tools/expected_output"

def validate_core(
  description : String,
  markdown : String,
  html : String,
  block_syntaxes = Array(Luce::BlockSyntax).new,
  inline_syntaxes = Array(Luce::InlineSyntax).new,
  extension_set : Luce::ExtensionSet? = nil,
  link_resolver : Luce::Resolver? = nil,
  image_link_resolver : Luce::Resolver? = nil,
  inline_only : Bool = false,
  enable_tag_filter : Bool = false
)
  it description do
    result = Luce.to_html(markdown,
      block_syntaxes: block_syntaxes,
      inline_syntaxes: inline_syntaxes,
      extension_set: extension_set,
      link_resolver: link_resolver,
      image_link_resolver: image_link_resolver,
      inline_only: inline_only,
      enable_tag_filter: enable_tag_filter
    )

    result.should eq html
  end
end

def data_cases_in_file(path : String, base_dir : String? = nil, & : DataCase ->)
  file = Path[path].basename.to_s.sub(/\..+$/, "")
  base_dir ||= Path[Path[path].dirname].relative_to(Path[Path[path].dirname].dirname).to_s

  lines = File.read_lines(path, encoding: "utf-8")

  front_matter = String::Builder.new

  i = 0

  until lines[i].starts_with? ">>>"
    front_matter << "#{lines[i]}\n"
    i += 1
  end

  while i < lines.size
    description = lines[i].sub(/>>>\s*/, "").strip
    i += 1
    skip = description.starts_with? "skip:"
    if description.empty?
      description = "line #{i + 1}"
    else
      description = "line #{i + 1}: #{description}"
    end

    input = String::Builder.new
    until lines[i].starts_with? "<<<"
      input.puts(lines[i])
      i += 1
    end

    expected_output = String::Builder.new
    i += 1
    while i < lines.size && !lines[i].starts_with? ">>>"
      expected_output.puts(lines[i])
      i += 1
    end

    # You can only read from a String::Builder once.
    # So make a copy.
    front_matter_s = front_matter.to_s
    front_matter = String::Builder.new front_matter_s

    data_case = DataCase.new(input.to_s, expected_output.to_s, base_dir, file,
      front_matter_s, description, skip)

    yield data_case
  end
end

def data_cases(dir : String, ext = "unit", recursive = true) : Array(DataCase)
  entries = Dir[dir + "/**/*"]
  results = [] of DataCase
  entries.each do |entry|
    next unless entry.ends_with? ext

    relative_dir = Path[Path[entry].dirname].relative_to(Path[dir].dirname).to_s

    data_cases_in_file(entry, relative_dir) { |data_case| results << data_case }
  end

  results.sort do |lhs, rhs|
    compare = lhs.directory <=> rhs.directory
    next compare if compare != 0
    lhs.file <=> rhs.file
  end
end

def data_cases_under(dir : String, ext = "unit", recursive = true, &)
  directory = Path[Dir.current, "spec", dir].to_s
  data_cases(directory, ext, recursive).each { |data_case| yield data_case }
end

# Runs tests defined in "*.unit" files inside directory *name*.
def test_directory(name : String, extension_set : Luce::ExtensionSet? = nil) : Nil
  data_cases_under(name) do |data_case|
    description = "#{data_case.directory}/#{data_case.file}.unit #{data_case.description}"

    inline_syntaxes = [] of Luce::InlineSyntax
    block_syntaxes = [] of Luce::BlockSyntax
    enable_tag_filter = false

    if data_case.file.ends_with? "_extension"
      extension = data_case.file[0...data_case.file.rindex("_extension")]

      case extension
      when "autolinks"
        inline_syntaxes << Luce::AutolinkExtensionSyntax.new
      when "strikethrough"
        inline_syntaxes << Luce::StrikethroughSyntax.new
      when "tables"
        block_syntaxes << Luce::TableSyntax.new
      when "disallowed_raw_html"
        enable_tag_filter = true
      else
        raise NotImplementedError.new("Extension set #{extension}")
      end
    end

    validate_core(
      description,
      data_case.input,
      data_case.expected_output,
      extension_set: extension_set,
      inline_syntaxes: inline_syntaxes,
      block_syntaxes: block_syntaxes,
      enable_tag_filter: enable_tag_filter)
  end
end

def test_file(file : String,
              block_syntaxes = Array(Luce::BlockSyntax).new,
              inline_syntaxes = Array(Luce::InlineSyntax).new)
  directory = Path[Dir.current, "spec"]
  data_cases_in_file(Path[directory, file].to_s) do |data_case|
    description = "#{data_case.directory}/#{data_case.file}.unit #{data_case.description}"
    validate_core(description, data_case.input, data_case.expected_output,
      block_syntaxes: block_syntaxes, inline_syntaxes: inline_syntaxes)
  end
end

class String < Reference
  # Converts this string to an array of `Luce::Line`.
  def to_lines : Array(Luce::Line)
    self.lines.map { |e| Luce::Line.new(e) }
  end
end
